package com.zt.questionnaire.service;

import com.zt.questionnaire.common.config.redis.CacheOperator;
import com.zt.questionnaire.common.enums.SexEnum;
import com.zt.questionnaire.common.utils.AssertUtils;
import com.zt.questionnaire.common.utils.JsonUtils;
import com.zt.questionnaire.common.utils.NumberArithmeticUtils;
import com.zt.questionnaire.common.utils.TimeUtils;
import com.zt.questionnaire.db.entity.ReportEntity;
import com.zt.questionnaire.db.entity.ReportOnboardingEntity;
import com.zt.questionnaire.db.mapper.ReportOnboardingMapper;
import com.zt.questionnaire.model.ReportReflect;
import com.zt.questionnaire.service.excel.HExcelQuestionnaireService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;

import java.math.BigDecimal;
import java.util.List;

/**
 * onboarding 报告表
 *
 * @author funnywus
 * @email funnywus@163.com
 * @date 2023-09-04 14:04:53
 */
@Slf4j
@Service
public class ReportOnboardingService {

    @Autowired
    private ReportOnboardingMapper mapper;

    @Autowired
    private CacheOperator redis;
    @Autowired
    private HExcelQuestionnaireService excelQuestionnaireService;
    @Lazy
    @Autowired
    private ReportService reportService;

    /**
     * 过期时间
     **/
    public long timeOut = TimeUtils.DAY;

    private static final String KEY = "onboarding_report@%s";

    /**
     * 组装主键key
     *
     * @return
     */
    private static String formatKey(Long reportId) {
        return String.format(KEY, reportId);
    }

    public List<ReportOnboardingEntity> getList() {
        Example example = new Example(ReportOnboardingEntity.class);
        Example.Criteria criteria = example.createCriteria();
        return mapper.selectByExample(example);
    }

    /**
     * 根据主键获取
     *
     * @param reportId
     * @return
     */
    public ReportOnboardingEntity get(Long reportId) {
        ReportOnboardingEntity obj = getRedis(reportId);
        if (obj != null) {
            return obj;
        }
        obj = getDb(reportId);
        if (obj != null) {
            setRedis(obj);
        }
        return obj;
    }

    public ReportOnboardingEntity getDb(Long reportId) {
        ReportOnboardingEntity obj = new ReportOnboardingEntity();
        obj.setReportId(reportId);
        return mapper.selectByPrimaryKey(obj);
    }

    /**
     * 保存
     *
     * @param obj
     * @return
     */
    public boolean save(ReportOnboardingEntity obj) {
        boolean is = saveDb(obj);
        if (!is) {
            return false;
        }
        setRedis(obj);
        return true;
    }

    public boolean saveDb(ReportOnboardingEntity obj) {
        return mapper.insert(obj) > 0;
    }

    /**
     * 更新
     *
     * @param obj
     * @return
     */
    public boolean update(ReportOnboardingEntity obj) {
        boolean is = updateDb(obj);
        if (!is) {
            return false;
        }
        setRedis(obj);
        return true;
    }

    public boolean updateDb(ReportOnboardingEntity obj) {
        return mapper.updateByPrimaryKey(obj) > 0;
    }

    /**
     * 根据主键删除
     *
     * @param reportId
     * @return
     */
    public boolean remove(Long reportId) {
        boolean is = removeDb(reportId);
        if (!is) {
            return false;
        }
        removeRedis(reportId);
        return true;
    }

    public boolean removeDb(Long reportId) {
        ReportOnboardingEntity obj = new ReportOnboardingEntity();
        obj.setReportId(reportId);
        return mapper.deleteByPrimaryKey(obj) > 0;
    }

    private ReportOnboardingEntity getRedis(Long reportId) {
        String formatKey = formatKey(reportId);
        return redis.getObject(formatKey, ReportOnboardingEntity.class, timeOut);
    }

    private void removeRedis(Long reportId) {
        String formatKey = formatKey(reportId);
        redis.remove(formatKey);
    }

    private void setRedis(ReportOnboardingEntity obj) {
        String formatKey = formatKey(obj.getReportId());
        redis.setObject(formatKey, obj, timeOut);
    }

    /**
     * 保存报告
     *
     * @return
     */
    public boolean save(ReportEntity base, ReportReflect reportReflect) {
        ReportOnboardingEntity onboardingReport = new ReportOnboardingEntity();

        BeanUtils.copyProperties(reportReflect, onboardingReport);
        // 处理报告基本信息
        handleBase(onboardingReport);

        log.info("保存报告 onboardingReport={}, base={}", JsonUtils.toString(onboardingReport), JsonUtils.toString(base));
        // 保存报告
        boolean is = reportService.save(base);
        AssertUtils.isFalse(is, "报告保存失败");

        onboardingReport.setReportId(base.getId());
        is = save(onboardingReport);
        AssertUtils.isFalse(is, "报告保存失败2");
        return true;
    }

    /**
     * 处理报告信息
     *
     * @param onboardingReport
     */
    private void handleBase(ReportOnboardingEntity onboardingReport) {
        log.info("处理报告基本信息 onboardingReport={}", JsonUtils.toString(onboardingReport));
        // 处理其他信息
//        String preferName = onboardingReport.getPreferName();
        Integer age = onboardingReport.getAge();
        Integer gender = onboardingReport.getGender();
        Integer height = onboardingReport.getHeight();
        Float currentWeight = onboardingReport.getCurrentWeight();

        // 用户目标体重
        Float userTargetWeight = onboardingReport.getUserTargetWeight();

        // BMI ：BMI= 体重 ÷( 身高(m) * 身高(m) ）
        // 身高: m
        float heightM = (float) height / 100;
        Float bmi = NumberArithmeticUtils.round(BigDecimal.valueOf(currentWeight / (heightM * heightM)), 1);
        // 基础代谢值
        Float basalMetabolic = getBasalMetabolic(gender, currentWeight, height, age);

        // 每日最低卡路里摄入,每日最高卡路里摄入
        ReportOnboardingEntity dailyCalNeedOR = getDailyCalNeed(gender, currentWeight, height, age, bmi);

        // 最小健康体重,最大健康体重
        // 最小体重 = (身高 * 身高 ）* 18.5 ~ 最大体重 =  (身高 * 身高 ）*23.9 (体重单位：kg；身高单位：m）
        int minAcceptableWeight = (int) (heightM * heightM * 18.5F);
        int maxAcceptableWeight = (int) (heightM * heightM * 23.9F);

        // 真实目标体重，如果
        //1. min_acceptable_weight<= user_target_weight <= max_acceptable_weight
        //直接存 user_target_weight
        //2. user_target_weight < min_acceptable_weight
        //直接存 min_acceptable_weight
        //3. user_target_weigh> max_acceptable_weight
        //直接存 user_target_weight
        float realTargetWeight = 0F;
        if (minAcceptableWeight <= userTargetWeight && userTargetWeight <= maxAcceptableWeight) {
            realTargetWeight = userTargetWeight;
        } else if (userTargetWeight < minAcceptableWeight) {
            realTargetWeight = minAcceptableWeight;
        } else {
            realTargetWeight = userTargetWeight;
        }


        // 理想减重量:（月）理想减重量（月） = 当前体重(current_weight) × 5% ± 1.5 ( 单位：kg/月)
        float monthlyLoseRate = currentWeight * 0.05F;
        float maxMonthlyLoseRate = monthlyLoseRate + 1.5F;
        float minMonthlyLoseRate = monthlyLoseRate - 1.5F;

        // 减重时间（月）：减重计划周期范围 =(当前体重(current_weight) - 真实目标体重(real_target_weight)）/  理想减重量（月） (monthly_lose_rate)
        //( 体重单位：kg)
        int loseTimeNeed = (int) ((currentWeight - userTargetWeight) / monthlyLoseRate);

        onboardingReport.setBmi(bmi);
        onboardingReport.setBasalMetabolic(basalMetabolic);

        onboardingReport.setMinDailyCalNeed(dailyCalNeedOR.getMinDailyCalNeed());
        onboardingReport.setMaxDailyCalNeed(dailyCalNeedOR.getMaxDailyCalNeed());

        onboardingReport.setMinAcceptableWeight(minAcceptableWeight);
        onboardingReport.setMaxAcceptableWeight(maxAcceptableWeight);

        onboardingReport.setRealTargetWeight(NumberArithmeticUtils.round(realTargetWeight, 1));

        onboardingReport.setMonthlyLoseRate(NumberArithmeticUtils.round(monthlyLoseRate, 1));
        onboardingReport.setMaxMonthlyLoseRate(NumberArithmeticUtils.round(maxMonthlyLoseRate, 1));
        onboardingReport.setMinMonthlyLoseRate(NumberArithmeticUtils.round(minMonthlyLoseRate, 1));
        onboardingReport.setLoseTimeNeed(loseTimeNeed);
        log.info("onboardingReport={}", JsonUtils.toString(onboardingReport));

    }

    /**
     * 获取每日 最低和最高 卡路里摄入
     * 最大值公式：
     * 女【最大值】=[655.1 + (9.563 × 体重/kg) + (1.850 × 身高/cm) - (4.676 × 年龄)]*1.2
     * 男【最大值】=[66.5 + (13.75 × =体重/kg) + (5.003 × 身高/cm) - (6.75 × 年龄)]*1.2
     * 最小值公式：
     * 【最小值】=【最大值】*70%
     * <p>
     * 特殊情况：
     * 1. 当用户的BMI值不同时，【最大值】有以下逻辑：
     * 正常18.5-25:  不变
     * 超重25.1-29.9：【最大值】-200
     * 1级肥胖30-34.9：【最大值】-300
     * 2级肥胖35-39.9: 【最大值】-400
     * 3级肥胖≥40：【最大值】-500
     * <p>
     * 2. 【最小值】
     * 男【最小值】低于1540 时，统一为 1540
     * 女【最小值】低于1320 时，统一为 1320
     *
     * @param gender
     * @param currentWeight
     * @param height
     * @param age
     * @return
     */
    private ReportOnboardingEntity getDailyCalNeed(Integer gender, Float currentWeight, Integer height, Integer age, Float bmi) {
        float minDailyCalNeed = 0F;
        float maxDailyCalNeed = 0F;

        // 最大值，基础值
        float maxBaseNum = 0F;
        if (SexEnum.isType(gender, SexEnum.MALE)) {
            // 男
            maxBaseNum = (66.5F + (13.75F * currentWeight) + (5.003F * height) - (6.75F * age)) * 1.2F;
        } else {
            // 女
            maxBaseNum = (655.1F + (9.563F * currentWeight) + (1.850F * height) - (4.676F * age)) * 1.2F;
        }
        // 最小值，基础值
        float minBaseNum = maxBaseNum * 0.7F;


        // 最大值的特殊情况
        //     * 2. 【最小值】
        //     * 男【最小值】低于1540 时，统一为 1540
        //     * 女【最小值】低于1320 时，统一为 1320
        if (bmi >= 18.5F && bmi <= 25F) {
            maxDailyCalNeed = maxBaseNum - 200F;
        } else if (bmi >= 25.1F && bmi <= 29.9F) {
            maxDailyCalNeed = maxBaseNum - 200F;
        } else if (bmi >= 30F && bmi <= 34.9F) {
            maxDailyCalNeed = maxBaseNum - 300F;
        } else if (bmi >= 35F && bmi <= 39.9F) {
            maxDailyCalNeed = maxBaseNum - 400F;
        } else if (bmi >= 40F) {
            maxDailyCalNeed = maxBaseNum - 500F;
        }

        // 最小值的特殊情况
        if (SexEnum.isType(gender, SexEnum.MALE)) {
            minDailyCalNeed = minBaseNum < 1540 ? 1540 : minBaseNum;
        } else {
            minDailyCalNeed = minBaseNum < 1320 ? 1320 : minBaseNum;
        }

        ReportOnboardingEntity onboardingReport = new ReportOnboardingEntity();
        onboardingReport.setMaxDailyCalNeed((int) maxDailyCalNeed);
        onboardingReport.setMinDailyCalNeed((int) minDailyCalNeed);

        return onboardingReport;
    }

    /**
     * 基础代谢值
     * 通过Onboarding 问卷用户填写的数据计算 ：
     * 男性基础代谢率（kcal/day）=10×体重（kg）+6.25×身高（cm）-5×年龄（岁）+5
     * 女性基础代谢率（kcal/day）=10×体重（kg）+6.25×身高（cm）-5×年龄（岁）-161
     *
     * @param gender
     * @param currentWeight
     * @param height
     * @param age
     * @return
     */
    private Float getBasalMetabolic(Integer gender, Float currentWeight, Integer height, Integer age) {
        // 基础代谢值
        float basalMetabolic = 0F;

        // 基础值
        float baseNum = 10F * currentWeight + 6.25F * height - 5F * age;
        if (SexEnum.isType(gender, SexEnum.MALE)) {
            // 男
            basalMetabolic = baseNum + 5F;
        } else {
            // 女
            basalMetabolic = baseNum - 161F;
        }
        basalMetabolic = NumberArithmeticUtils.round(BigDecimal.valueOf(basalMetabolic), 1);
        return basalMetabolic;
    }
}


